package com.wg.core.script;

import cn.hutool.core.compiler.JavaSourceCompiler;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.watch.SimpleWatcher;
import cn.hutool.core.io.watch.WatchMonitor;
import cn.hutool.core.io.watch.WatchUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path;
import java.nio.file.WatchEvent;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import com.wg.core.exception.ScriptNotFoundException;

/**
 * 脚本动态加载器（支持热更新、文件监控、编译检查，线程安全）
 *
 * @author ChenLong
 */
public class ScriptJavaLoader {
    private static final Log log = LogFactory.get();
    /**
     * 防抖延迟（毫秒）
     */
    private static final long COMPILE_DELAY = 1000;
    /**
     * 脚本文件后缀
     */
    private static final String JAVA_SUFFIX = ".java";

    private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
            r -> new Thread(r, "script-compile-scheduler"));
    private final Map<String, ScriptBean> scriptBeanMap = new ConcurrentHashMap<>(128);
    /**
     * 编译全局锁
     */
    private final ReentrantLock compileLock = new ReentrantLock();
    /**
     * 线程安全的文件修改时间记录
     */
    private final Map<String, Long> lastModifyTimeMap = new ConcurrentHashMap<>();
    /**
     * 防抖任务（文件路径 -> 类名）
     */
    private final Map<String, String> compileTasks = new ConcurrentHashMap<>();
    /**
     * 脚本根目录
     */
    private String javaFilePath;
    /**
     * 文件监控器
     */
    private WatchMonitor watchMonitor;
    /**
     * 父类加载器（用于加载依赖）
     */
    private ClassLoader parentClassLoader;

    public ScriptJavaLoader() {
        this.parentClassLoader = Thread.currentThread().getContextClassLoader();
    }

    /**
     * 初始化脚本加载器（启动文件监控）
     *
     * @param javaFilePath 脚本根目录（绝对路径）
     * @throws IllegalArgumentException 若目录不存在或非目录
     */
    public void initialize(String javaFilePath) {
        Objects.requireNonNull(javaFilePath, "script source path cannot be null");
        File rootDir = new File(javaFilePath);
        if (!rootDir.exists() || !rootDir.isDirectory()) {
            throw new IllegalArgumentException("script source path does not exist or is not a directory: " + javaFilePath);
        }
        this.javaFilePath = FileUtil.normalize(rootDir.getAbsolutePath());

        // 启动文件监控
        this.watchMonitor = WatchUtil.createAll(this.javaFilePath, Integer.MAX_VALUE, new SimpleWatcher() {
            @Override
            public void onModify(WatchEvent<?> event, Path currentPath) {
                handleFileChange(currentPath);
            }
        });
        this.watchMonitor.start();
        log.info("ScriptJavaLoader initialized, watching directory: {}", this.javaFilePath);
    }

    /**
     * 处理文件变更（创建/修改/删除）
     *
     * @param currentPath 文件路径
     * @param isDelete    是否为删除事件
     */
    private void handleFileChange(Path currentPath) {
        try {
            String currentPathStr = currentPath.toString().replace(File.separatorChar, '/');
            String rootPathStr = javaFilePath.replace(File.separatorChar, '/');
            if (!currentPathStr.startsWith(rootPathStr)) {
                return; // 非监控目录文件，忽略
            }

            String relativePath = currentPathStr.substring(rootPathStr.length() + 1);
            if (relativePath.endsWith(JAVA_SUFFIX)) {
                String className = relativePath
                        .substring(0, relativePath.length() - JAVA_SUFFIX.length())
                        .replace('/', '.');

                scheduleCompile(className, currentPathStr);
            }
        } catch (Exception e) {
            log.error("ScriptJavaLoader failed to handle file change: {}", currentPath, e);
        }
    }

    /**
     * 防抖编译调度（同一文件多次修改仅执行一次编译）
     *
     * @param className   类名
     * @param filePathStr 文件路径
     */
    private void scheduleCompile(String className, String filePathStr) {
        // 取消已存在的任务，重新调度
        compileTasks.put(filePathStr, className);
        scheduler.schedule(() -> {
            String taskClassName = compileTasks.remove(filePathStr);
            if (taskClassName != null) {
                loadScript(Collections.singletonList(taskClassName), true);
            }
        }, COMPILE_DELAY, TimeUnit.MILLISECONDS);
    }

    /**
     * 解析目录下所有Java文件的类名
     *
     * @param folderPath 相对根目录的包路径（如"com.wg.game.script"）
     * @return 类名列表（全类名）
     */
    public List<String> parseFolderJavaFile(String folderPath) {
        Objects.requireNonNull(folderPath, "script package path cannot be null");
        File targetDir = new File(javaFilePath, folderPath.replace('.', File.separatorChar));
        if (!targetDir.exists() || !targetDir.isDirectory()) {
            log.warn("script package directory does not exist: {}", targetDir.getAbsolutePath());
            return Collections.emptyList();
        }
        List<String> scriptNames = new ArrayList<>();
        parseClassName(scriptNames, folderPath, targetDir);
        return scriptNames;
    }

    /**
     * 递归解析目录下的Java类名（修正包路径拼接逻辑）
     *
     * @param scriptNames 结果列表
     * @param basePkg     基础包名（如"com.wg.game"）
     * @param dir         当前目录
     */
    private void parseClassName(List<String> scriptNames, String basePkg, File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            log.warn("ScriptJavaLoader failed to read directory: {}", dir.getAbsolutePath());
            return;
        }

        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile() && fileName.endsWith(JAVA_SUFFIX)) {
                // 构建全类名（basePkg + 类名）
                String className = basePkg.isEmpty()
                        ? fileName.substring(0, fileName.length() - JAVA_SUFFIX.length())
                        : basePkg + "." + fileName.substring(0, fileName.length() - JAVA_SUFFIX.length());
                scriptNames.add(className);
            } else if (file.isDirectory()) {
                // 递归处理子目录（拼接包名）
                String subPkg = basePkg.isEmpty() ? fileName : basePkg + "." + fileName;
                parseClassName(scriptNames, subPkg, file);
            }
        }
    }

    /**
     * 全量加载所有脚本
     */
    public void loadScriptAll() {
        List<String> allScripts = parseFolderJavaFile(""); // 加载根目录下所有脚本
        loadScript(allScripts, false);
        log.info("ScriptJavaLoader loaded {} scripts", allScripts.size());
    }

    /**
     * 调用脚本
     *
     * @param scriptName 脚本全类名
     * @param arg        参数容器（不可为null）
     * @return 执行结果
     * @throws Exception 脚本未找到或执行异常
     */
    public Object call(String scriptName, ScriptArgs arg) throws Exception {
        Objects.requireNonNull(arg, "ScriptArgs cannot be null");
        IScript script = getScript(scriptName);
        if (script == null) {
            throw new ScriptNotFoundException("Script not found: " + scriptName);
        }
        return script.call(scriptName, arg);
    }

    /**
     * 加载指定脚本列表（支持热更新）
     *
     * @param scriptNames 脚本全类名列表
     * @param isReload    是否为重载（true时检查文件修改时间）
     */
    public void loadScript(List<String> scriptNames, boolean isReload) {
        compileLock.lock();
        try {
            for (String scriptName : scriptNames) {
                if (scriptName == null || scriptName.trim().isEmpty()) {
                    log.warn("ScriptJavaLoader skipped empty script name");
                    continue;
                }
                try {
                    loadSingleScript(scriptName.trim(), isReload);
                } catch (Exception e) {
                    log.error("ScriptJavaLoader failed to load script: {}", scriptName, e);
                }
            }
        } finally {
            compileLock.unlock();
        }
    }

    /**
     * 加载单个脚本
     */
    private void loadSingleScript(String scriptName, boolean isReload) throws Exception {
        // 检查Java文件是否存在
        String javaFilePath = getJavaFilePath(scriptName);
        File javaFile = new File(javaFilePath);
        if (!javaFile.exists() || !javaFile.isFile() || !javaFile.canRead()) {
            log.error("ScriptJavaLoader failed to read Java file: {}", javaFilePath);
            return;
        }

        // 检查文件是否修改（仅重载时校验）
        if (isReload) {
            long lastModified = javaFile.lastModified();
            Long prevModified = lastModifyTimeMap.get(scriptName);
            if (prevModified != null && lastModified <= prevModified) {
                log.debug("ScriptJavaLoader skipped unmodified script: {}", scriptName);
                return;
            }
            lastModifyTimeMap.put(scriptName, lastModified);
        }

        // 编译Java文件（使用自定义类加载器避免冲突）
        String sourceCode = FileUtil.readUtf8String(javaFile);
        Class<?> clazz = compileJavaSource(scriptName, sourceCode);
        if (clazz == null) {
            log.error("ScriptJavaLoader failed to compile Java file: {}", scriptName);
            return;
        }

        // 校验是否实现IScript接口
        if (!IScript.class.isAssignableFrom(clazz)) {
            log.error("ScriptJavaLoader class {} does not implement IScript interface", scriptName);
            return;
        }

        // 创建脚本实例并初始化
        IScript newScript = (IScript) clazz.getDeclaredConstructor().newInstance();
        newScript.init(); // 初始化可能抛异常，需上层捕获

        // 替换旧实例并销毁
        ScriptBean oldBean = scriptBeanMap.get(scriptName);
        scriptBeanMap.put(scriptName, new ScriptBean().setName(scriptName).setScript(newScript));

        // 销毁旧实例（异步执行，避免阻塞新实例加载）
        if (oldBean != null) {
            scheduler.execute(() -> {
                try {
                    oldBean.getScript().destroy();
                    log.debug("ScriptJavaLoader destroyed old script instance: {}", scriptName);
                } catch (Exception e) {
                    log.error("ScriptJavaLoader failed to destroy old script instance: {}", scriptName, e);
                }
            });
        }

        log.info("ScriptJavaLoader loaded script successfully: {}", scriptName);
    }

    /**
     * 编译Java源代码（使用自定义类加载器）
     */
    private Class<?> compileJavaSource(String className, String sourceCode) {
        try {
            // 自定义类加载器：每个脚本类使用独立加载器，避免重复加载冲突
            URL[] classPath = {new File(javaFilePath).toURI().toURL()};
            ClassLoader customClassLoader = new URLClassLoader(classPath, parentClassLoader) {
                @Override
                public Class<?> loadClass(String name) throws ClassNotFoundException {
                    // 优先加载当前编译的类，避免加载旧版本
                    if (name.equals(className)) {
                        return findClass(name);
                    }
                    return super.loadClass(name);
                }
            };

            // 使用Hutool编译并加载类
            JavaSourceCompiler compiler = JavaSourceCompiler.create(customClassLoader);
            return compiler.addSource(className, sourceCode).compile().loadClass(className);
        } catch (Exception e) {
            log.error("ScriptJavaLoader failed to compile Java source: {}\nSource: {}", className, sourceCode, e);
            return null;
        }
    }

    /**
     * 获取Java文件完整路径
     */
    private String getJavaFilePath(String scriptName) {
        return javaFilePath + File.separator + scriptName.replace('.', File.separatorChar) + JAVA_SUFFIX;
    }

    /**
     * 根据脚本名获取脚本实例
     */
    public IScript getScript(String scriptName) {
        ScriptBean bean = scriptBeanMap.get(scriptName);
        return bean != null ? bean.getScript() : null;
    }

    /**
     * 销毁加载器（释放所有资源）
     */
    public void destroy() {
        // 停止监控
        if (watchMonitor != null) {
            watchMonitor.close();
            watchMonitor = null;
        }

        // 停止调度器
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(1, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
        }

        // 销毁所有脚本实例
        scriptBeanMap.values().forEach(bean -> {
            try {
                bean.getScript().destroy();
            } catch (Exception e) {
                log.error("ScriptJavaLoader failed to destroy script instance: {}", bean.getName(), e);
            }
        });
        scriptBeanMap.clear();
        lastModifyTimeMap.clear();
        compileTasks.clear();

        log.info("ScriptJavaLoader destroyed");
    }
}